<?php
/**
 * DMFramework
 * 
 * @license Copyright 2012 Dillen Meijboom under the Apache license version 2.0 (http://apache.org/licenses/LICENSE-2.0)
 * @author Dillen Meijboom <info@dillenm.nl>
 * @url http://dmframework.nl
 */

/**
 * The MAIL class can be used to send mailes, add attachments e.t.c.
 * 
 * @license Copyright 2012 Dillen Meijboom under the Apache license version 2.0 (http://apache.org/licenses/LICENSE-2.0)
 * @author Dillen Meijboom <info@dillenm.nl>
 * @package System.Library
 */
class Dmf_Mail
{
    /**
     * The subject of the e-mail
     * 
     * @var string
     */
    private $_subject;
    
    /**
     * The content of the e-mail
     * 
     * @var string
     */
    private $_content;
    
    /**
     * The e-mail headers
     * 
     * @var array
     */
    private $_headers = array();
    
    /**
     * The recipients
     * 
     * @var array
     */
    private $_senders = array();
    
    /**
     * From e-mail address
     * 
     * @var string 
     */
    private $_from;
    
    /**
     * The e-mail client type
     * 
     * @var const
     */
    private $_type = 6;
    
    /**
     * Plain e-mail content
     * 
     * @var string
     */
    private $_plainContent;
    
    /**
     * The sendmail path
     * 
     * @var string
     */
    private $_sendmailPath;
    
    /**
     * The SMTP resource
     * 
     * @var resource
     */
    private $_smtpResource;
    
    /**
     * SMTP timeout
     * 
     * @var int
     */
    private $_smtpTimeout = 1;
    
    /**
     * The SMTP response
     * 
     * @var string
     */
    private $_smtpResponse;
    
    /**
     * All attachments
     * 
     * @var array
     */
    private $_attachments = array();
    
    /**
     * Use the default mail() function
     * 
     * @var int 
     */
    const MAIL = 6;
    
    /**
     * Use sendmail directly
     * 
     * @var int 
     */
    const SENDMAIL = 8;
    
    /**
     * Use a SMTP server
     *  
     * @var int
     */
    const SMTP = 10;
    
    /**
     * Create new e-mail instance
     * 
     * @return Dmf_Mail 
     */
    public static function create()
    {
        $mail = new Dmf_Mail;
        
        return $mail;
    }
    
    /**
     * Use sendmail directly
     * 
     * @param string $url
     * @return Dmf_Mail 
     */
    public function useSendMail( $url = '/usr/sbin/sendmail' )
    {
        $this->_type = self::SENDMAIL;
        $this->_sendmailPath = $url;
        
        return $this;
    }
    
    /**
     * Wait for SMTP response
     * 
     * @param string $string 
     */
    private function _waitForResponse( $string = '220' )
    {
        $response = fgets( $this->_smtpResource, 4096 );
        
        if ( ! strstr( $response, $string ) )
        {
            usleep( 500 );
            $this->_waitForResponse( $string );
        }
    }
    
    /**
     * Add CC e-mail
     * 
     * @param string $cc 
     */
    public function addCc( $cc )
    {
        $this->_headers[] = 'CC: ' . $cc;
    }
    
    /**
     * Add BCC e-mail
     * 
     * @param string $bcc 
     */
    public function addBcc( $bcc )
    {
        $this->_headers[] = 'BCC: ' . $bcc;
    }
    
    /**
     * FPUTS with output debugging
     * 
     * @param resource $resource
     * @param string $command 
     */
    private function _put( $resource, $command )
    {
        @fputs( $resource, $command );
        
        $this->_smtpResponse .= fgets( $resource, 4096 );
    }
    
    /**
     * Use a SMTP server
     * 
     * @param string $server
     * @param string $user
     * @param string $pass
     * @param int $port
     * @return Dmf_Mail
     * @throws Exception 
     */
    public function useSmtp( $server, $user, $pass, $port = 25 )
    {
        $this->_type = self::SMTP;
        
        $this->_smtpResource = @fsockopen( $server, $port, $error_number, $error_string, $this->_smtpTimeout );
        
        if ( $this->_smtpResource === false )
        {
            throw new Exception( 'Could not connect to SMTP server: "' . $server . '" on port: "' . $port . '" with message: "' . $error_string . '".' );
        }
        
        $this->_waitForResponse();
        
        $this->_put( $this->_smtpResource, 'HELO ' . $_SERVER['REMOTE_ADDR'] . "\n" );
        $this->_put( $this->_smtpResource, 'EHLO ' . $_SERVER['REMOTE_ADDR'] . "\n" );
        
        $this->_waitForResponse( 'HELP' );
        
        $this->_put( $this->_smtpResource, 'AUTH LOGIN ' . "\n" );
        $this->_put( $this->_smtpResource, base64_encode( $user ) . "\n" );
        $this->_put( $this->_smtpResource, base64_encode( $pass ) . "\n" );
        
        return $this;
    }
    
    /**
     * Set from e-mail
     * 
     * @param string $from
     * @return Dmf_Mail 
     */
    public function setFrom( $from )
    {
        $this->_from = $from;
        
        return $this;
    }
    
    /**
     * Set the e-mail subject
     * 
     * @param string $subject
     * @return Dmf_Mail 
     */
    public function setSubject( $subject )
    {
        $this->_subject = $subject;
        
        return $this;
    }
    
    /**
     * Add a custom header
     * 
     * @param string $header
     * @return Dmf_Mail 
     */
    public function addHeader( $header )
    {
        $this->_headers[] = $header;
        
        return $this;
    }
    
    /**
     * Set the HTML or PLAIN content
     * 
     * @param string $content
     * @param boolean $plain
     * @return \Dmf_Mail 
     */
    public function setContent( $content, $plain = false )
    {
        if ( $plain === true )
        {
            $this->_plainContent = $content;
        }
        else
        {
            $this->_content = preg_replace( '/="([^"]*)"/', '=3D"$1"', quoted_printable_encode( $content ) );
        }
        
        return $this;
    }
    
    /**
     * Add sender
     * 
     * @param string $sender
     * @return Dmf_Mail 
     */
    public function addSender( $sender )
    {
        $this->_senders[] = $sender;
        
        return $this;
    }
    
    /**
     * Mail using the built-in PHP function
     * 
     * @param array $senders
     * @param string $subject
     * @param string $message
     * @param array $headers
     * @throws Exception 
     */
    private function _mail( $senders, $subject, $message, $headers )
    {
        foreach( $senders as $sender )
        {
            if ( ! @mail( $sender, $subject, $message, implode( "\n", $headers ) ) )
            {
                throw new Exception( 'Could send mail to: "' . $sender . '" using mail().' );
            }
        }
    }
    
    /**
     * Get the from e-mail header
     * 
     * @param array $headers
     * @return boolean|string
     */
    private function _getFrom( array $headers )
    {
        foreach( $headers as $header )
        {
            if ( strstr( $header, 'From' ) )
            {
                return str_replace( 'From:', '', $header );
            }
        }
        
        return false;
    }
    
    /**
     * Mail using a SMTP server
     *  
     * @param array $senders
     * @param string $subject
     * @param string $message
     * @param array $headers 
     */
    private function _smtpmail( $senders, $subject, $message, $headers )
    {
        $headers[] = 'Subject: ' . $subject . "\n";
        $headers[] = 'To: ' . "\n";
        
        $this->_put( $this->_smtpResource, 'MAIL From: ' . $this->_getFrom( $headers ) . "\n" );
        
        foreach( $senders as $sender )
        {
             $this->_put( $this->_smtpResource, 'RCPT To: ' . $sender . "\n" );
        }
        
        $this->_put( $this->_smtpResource, 'DATA ' . "\n" );
        
        foreach( $headers as $header )
        {
            $this->_put( $this->_smtpResource, 'DATA ' . "\t" . $header . "\n" ); 
        }
        
        foreach( explode( "\n", $message ) as $messagePart )
        {
            $this->_put( $this->_smtpResource, 'DATA ' . $messagePart . "\n" );
        }
        
        $this->_put( $this->_smtpResource, "\n" . '.' . "\n" );
        $this->_put( $this->_smtpResource, 'QUIT' . "\n" );
        
        fclose( $this->_smtpResource );
    }
    
    /**
     * Mail using sendmail directly
     * 
     * @param array $senders
     * @param string $subject
     * @param string $message
     * @param array $headers
     * @throws Exception 
     */
    private function _sendmail( $senders, $subject, $message, $headers )
    {
        $sendmail = sprintf( '%s -f %s -t', $this->_sendmailPath, $this->_getFrom( $headers ) );
        
        if ( ( $email = @popen( $sendmail, 'w' ) ) === false )
        {
            throw new Exception( 'Could not send mail using sendmail path: "' . $this->_sendmailPath . '"' );
        }
        
        $headers[] = 'To: ' . implode( ', ', $senders );
        $headers[] = 'Subject: ' . $subject . "\n";
        
        fputs( $email, implode( "\n", $headers ) );
        fputs( $email, $message );
        pclose( $email );
    }
    
    /**
     * Add a attachment
     * 
     * @param string $filename
     * @param string $name
     * @param string $mimetype
     * @return Dmf_Mail
     * @throws Dmf_Exception_FileNotFound 
     */
    public function addAttachment( $filename, $name, $mimetype = 'text/plain' )
    {
        if ( ! file_exists( $filename ) )
        {
            throw new Dmf_Exception_FileNotFound( 'Attachment: "' . $filename . '" not found.' );
        }
        
        $this->_attachments[] = array(
            'filename' => $filename,
            'name'     => $name,
            'mimetype' => $mimetype
        );
        
        return $this;
    }
    
    /**
     * Add an attachment from source
     * 
     * @param string $content
     * @param string $name
     * @param string $mimetype
     * @return Dmf_Mail 
     */
    public function addAttachmentFromSource( $content, $name, $mimetype = 'text/plain' )
    {
        $this->_attachments[] = array(
            'filename' => null,
            'content'  => $content,
            'name'     => $name,
            'mimetype' => $mimetype
        );
        
        return $this;
    }
    
    /**
     * Get attachment source (chunked + base64'd)
     * 
     * @param array $attachment
     * @return string 
     */
    private function _getAttachment( $attachment )
    {
        if ( isset( $attachment['content'] ) )
        {
            return str_replace( "\r", '', chunk_split( base64_encode( $attachment['content'] ) ) );
        }
        else
        {
            return str_replace( "\r", '', chunk_split( base64_encode( file_get_contents( $attachment['filename'] ) ) ) );
        }
    }
    
    /**
     * Send the actual e-mail
     */
    public function send()
    {
        if ( count( $this->_senders ) === 0 )
        {
            throw new Exception( 'Can\'t send an e-mail withouth the "to" e-mail field.' );
        }
        
        $boundary = 'boundary_' . uniqid() . rand( 0, 9999 );
        $boundary_files = 'boundary_' . uniqid() . rand( 0, 9999 );
        $headers = $this->_headers;
        $message = '';
        
        //Set additional headers
        $headers[] = 'Date: ' . date( 'r' );
        $headers[] = 'X-Mailer: PHP/' . phpversion();
        $headers[] = 'Content-Transfer-Encoding: Quoted-printable';
        $headers[] = 'From: ' . $this->_from;
        $headers[] = 'MIME-Version: 1.0';
        $headers[] = 'Content-Type: multipart/mixed; boundary=' . $boundary_files;
        
        //Main boundary
        $message .= '--' . $boundary_files . "\n" . 'Content-type: multipart/alternative; boundary=' . $boundary . "\n\n";
        
        //Create mixed message
        $message .= '--' . $boundary . "\n" . 'Content-Type: text/plain; charset=ISO-8859-1; format=flowed; delsp=yes';
        $message .= "\n" . 'Content-Transfer-Encoding: Quoted-printable' . "\n" . 'Content-Disposition: inline' . "\n\n";
        $message .= wordwrap( ( $this->_plainContent === null ? strip_tags( $this->_content ) : $this->_plainContent ), 70 );
        
        $message .= "\n" . "\n" . '--' . $boundary. "\n" . 'Content-Type: text/html; charset=ISO-8859-1';
        $message .= "\n" . 'Content-Transfer-Encoding: Quoted-printable' . "\n" . 'Content-Disposition: inline' . "\n\n";
        $message .= wordwrap( $this->_content, 70 ) . "\n\n";
        
        $message .= '--' . $boundary . '--' . "\n";
        
        //Add all attachments
        foreach( $this->_attachments as $attachment )
        {
            $message .= "\n" . '--' . $boundary_files. "\n" . 'Content-Type: ' . $attachment['mimetype'] . '; ' . "\n\t" . ' name="' . $attachment['name'] . '"' . "\n";
            $message .= 'Content-Transfer-Encoding: base64' . "\n";
            $message .= 'Content-Disposition: attachment;' . "\n\t" . 'filename="' . $attachment['name'] . '"' . "\n\n";
            
            $message .= $this->_getAttachment( $attachment ) . "\n";
        }
        
        $message .= '--' . $boundary_files . '--';
        
        switch( $this->_type )
        {
            case self::MAIL:
                $this->_mail( $this->_senders, $this->_subject, $message, $headers );
                break;
            case self::SENDMAIL:
                $this->_sendmail( $this->_senders, $this->_subject, $message, $headers );
                break;
            case self::SMTP:
                $this->_smtpmail( $this->_senders, $this->_subject, $message, $headers );
                break;
        }
    }
}